package com.hmdp.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.Voucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.SimpleRedisLock;
import com.hmdp.utils.UserHolder;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Resource
    StringRedisTemplate stringRedisTemplate;
    @Resource
    ISeckillVoucherService seckillVoucherService;
    @Resource
    RedisIdWorker redisIdWorker;
    @Autowired
    IVoucherOrderService voucherOrderService;
    @Resource
    RedissonClient redissonClient;


    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;

    static {
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("Seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }


//    创建一个阻塞队列 后面跟上容量
    private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024);


/**
     * 第二种方式 将一人一单和查询库存的方式放在redis中去进行操作
     * 1.新增秒杀优惠券的同时，将优惠券的信息保存在redis中
     * 2.基于Lua脚本，判断秒杀库存、一人一单、决定用户是否成功
     * 3.如果抢购成功，将优惠券id 跟用户id 放在阻塞队列中
     * 4.开启线程任务，不断从阻塞队列中获取消息，实现异步下单功能
     * @param voucherId
     * @return
     *//*

    @Override
    public Result secKillVoucher(Long voucherId) {
        //获取用户
        Long userId = UserHolder.getUser().getId();
        long orderId = redisIdWorker.nextId("order");
        // 1.执行lua脚本
        Long result = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(), userId.toString(), String.valueOf(orderId)
        );
        int r = result.intValue();
        // 2.判断结果是否为0
        if (r != 0) {
            // 2.1.不为0 ，代表没有购买资格
            return Result.fail(r == 1 ? "库存不足" : "不能重复下单");
        }
//        为0 有购买资格，把下单信息存储在阻塞队列中
        VoucherOrder voucherOrder = new VoucherOrder();
        long orderid = redisIdWorker.nextId("order");
        voucherOrder.setId(orderid);
        //        生成优惠券id
        voucherOrder.setVoucherId(voucherId);
        //        生成用户id
        voucherOrder.setUserId(userId);
        //tip 保存阻塞队列
        orderTasks.add(voucherOrder);
        // 3.返回订单id
        return Result.ok(orderId);
    }
}
*/


    @Override
    @Transactional
    public Result secKillVoucher(Long voucherId) {
        //        1.根据用户提交的优惠券id进行查询信息
        LambdaQueryWrapper<SeckillVoucher> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SeckillVoucher::getVoucherId, voucherId);
        SeckillVoucher voucher = seckillVoucherService.getOne(wrapper);

        LocalDateTime beginTime = voucher.getBeginTime();
        LocalDateTime endTime = voucher.getEndTime();

        //        2.判断秒杀是否开始
        if (beginTime.isAfter(LocalDateTime.now())) {
            return Result.fail("还未到抢购时间");
        }
        //        3.判断秒杀是否结束
        if (endTime.isBefore(LocalDateTime.now())) {
            return Result.fail("抢购时间已经过了");
        }

        //        5.库存不足返回错误信息
        if (voucher.getStock() < 1) {
            return Result.fail("库存不足");
        }

        return creatVoucherOrd(voucherId);

    }


    @Transactional
    public Result creatVoucherOrd(Long vouncherId) {
        Long userid = UserHolder.getUser().getId();
        //      创建锁对象
        RLock lock = redissonClient.getLock("lock:order:" + userid);
        boolean tryLock = lock.tryLock();
        if (!tryLock) {
            return Result.fail("当前活动太火爆了，请再次尝试");
        }

        try {
            LambdaQueryWrapper<VoucherOrder> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(VoucherOrder::getUserId, userid);
            List<VoucherOrder> list = voucherOrderService.list(wrapper);

    //        tip 实现一人一单
    /**
     *  TODO 一人一单的多线程访问问题
     * 问题分析：当多个线程一起来访问，都去访问发现数据库中并没有该用户的订单 此时数据库会产生多个订单
     * 解决方法： 加锁
     * 版本迭代：
     *
     * 一代：使用悲观锁（乐观锁比较适合更新数据，此时需要插入数据）
     * public synchronized Result createVoucherOrder(Long voucherId) { tip 直接在方法上加上悲观锁
     * 淘汰原因：锁的粒度太粗了
     *
     * 二代：synchronized(userId.toString().intern()){
     * tip 对代码块上加synchronize 加上intern 确保为同一个方法
     *
     * 三代：将当前方法用锁给包裹起来 保证了锁的粒度，有保证了事务不会出现问题
     * 在retern 之前加锁
     *
     * 四代：由于没有解决分布式系统中的问题 使用redis的分布式锁 定义接口跟实现类
     * 确保当前为同一个线程
     * tip SimpleRedisLock lock = new SimpleRedisLock(stringRedisTemplate,"order:"+userid);
     * 存在问题
     * 1.存在误删情况（tip 删除锁时判断是否为当前线程的锁 即可）
     * 2.redis的分布式锁没有解决原子性 所以要使用lua脚本 确保操作的原子性
     *！！！
     * 四个特性没有满足
     * 1.不可重入
     * 2.不可重试
     * 3.超时释放
     * 4.主从一致性
     *
     * 五代：使用redisson
     * tip 秒杀优化方案
     * 当一人一单跟查询库存这些操作都放在数据库中，会影响秒杀的效率
     * 可以把 这些操作放在redis中 大大提高效率
     *
     *
     *
     if (list.size()>0)
     {
     return Result.fail("同一个用户只能领取一张");
     }
     //        tip 扣除库存
     //        加入乐观锁机制 解决超卖问题

     /**
     * TODO 超卖问题的发生原因
     * 当代码执行中，多个线程一起来访问，当一个线程过来查询库存，
     * 还没有扣减，第二个线程也来查询，也去数据库进行了扣减，进行了超卖
     * 解决方法：进行加锁 悲观锁或者乐观锁
     * tip 在扣除库存的时候进行一个判断，是否可以扣减库存
     *
     **/

        boolean success = seckillVoucherService.update()
                .setSql("stock = stock -1")
                .eq("voucher_id", vouncherId).gt("stock", 0).update();

        if (!success) {
            return Result.fail("库存不足");
        }

        //        tip 创建订单 生成订单号
        VoucherOrder voucherOrder = new VoucherOrder();
        long orderid = redisIdWorker.nextId("order");
        voucherOrder.setId(orderid);
        //        生成优惠券id
        voucherOrder.setVoucherId(vouncherId);
        //        生成用户id
        voucherOrder.setUserId(userid);
        this.save(voucherOrder);
        return Result.ok(orderid);
    }
    finally
    {
        lock.unlock();
    }

    }
}


